#pragma once

#include "es_log.h"
#include <cstddef>
#include <list>
#include <queue>

namespace esvr {

class BlockFactory;

/*
**avoid io stream copy.
*/
template <size_t BLOCK_SIZE = 1024> class MemBlock {

  friend class BlockFactory;

private:
  char *buffer = NULL;
  size_t ptail = 0;
  size_t phead = 0;

public:
  MemBlock() { this->buffer = new char[BLOCK_SIZE]; }

  MemBlock(const MemBlock &) = delete;

  MemBlock(const MemBlock &&) = delete;

  MemBlock &operator=(const MemBlock &) = delete;

  ~MemBlock() {
    if (buffer) {
      delete[] buffer;
      buffer = NULL;
      reset();
    }
  }

  void reset() { phead = ptail = 0; }

  size_t writable_size() const { return BLOCK_SIZE - ptail; }

  size_t readable_size() const { return ptail - phead; }

  char *readable_buffer() const {
    if (phead >= ptail)
      return NULL;
    return buffer + phead;
  }

  char *writable_bufer() const {
    if (ptail >= BLOCK_SIZE)
      return NULL;
    return buffer + ptail;
  }

  void increase_readable(size_t len) {
    ptail += len;
    if (ptail > BLOCK_SIZE)
      ptail = BLOCK_SIZE;
  }

  void decrease_readable(size_t len) {
    phead += len;
    if (phead > ptail)
      phead = ptail;
  }

  static size_t block_size() { return BLOCK_SIZE; }
};

/*
**manage all mem blocks.
*/
class BlockFactory {

private:
  std::queue<MemBlock<> *> queue;

  BlockFactory(const BlockFactory &) = delete;

  BlockFactory(const BlockFactory &&) = delete;

  BlockFactory &operator=(const BlockFactory &) = delete;

  BlockFactory() {}

  ~BlockFactory() { clear(); }

  void clear() {
    while (!queue.empty()) {
      auto front = queue.front();
      queue.pop();
      delete front;
    }
  }

public:
  static BlockFactory *instance() {
    static BlockFactory inst;
    return &inst;
  }
  static BlockFactory *get_instance() { return instance(); }

  static void clear_instance() {
    auto inst = instance();
    inst->clear();
  }

  MemBlock<> *alloc_block() {
    MemBlock<> *block = NULL;
    if (!queue.empty()) {
      block = queue.front();
      queue.pop();
    } else
      block = new MemBlock<>;
    LOG_DEBUG("alloc_block:%x", block);
    return block;
  }

  void free_block(MemBlock<> *block) {
    LOG_DEBUG("free_block:%x", block);
    queue.push(block);
  }

  void free_blocks(std::list<MemBlock<> *> &queue) {
    while (!queue.empty()) {
      auto head = queue.front();
      this->queue.push(head);
      LOG_DEBUG("free_block:%x", head);
      queue.pop_front();
    }
  }
};

/*
**socket private block queue.
*/
class BlockList {

private:
  std::list<MemBlock<> *> list;

  BlockList(const BlockList &) = delete;

  BlockList(const BlockList &&) = delete;

  BlockList &operator=(const BlockList &) = delete;

public:
  MemBlock<> *readable_buffer() {
    while (!list.empty()) {
      auto front = list.front();
      if (front->readable_size() > 0)
        return front;
      BlockFactory::instance()->free_block(front);
      list.pop_front();
    }
    return NULL;
  }

  MemBlock<> *writable_bufer() {
    if (!list.empty()) {
      auto back = list.back();
      if (back->writable_size() > 0)
        return back;
    }
    MemBlock<> *block = BlockFactory::instance()->alloc_block();
    list.push_back(block);
    return block;
  }

  BlockList() {}

  ~BlockList() {
    if (!list.empty()) {
      BlockFactory::instance()->free_blocks(list);
    }
  }
};
}
